#include <string.h>
#include <stdio.h>
#include "MemoryManager.h"
/**
 * Forward declarations
 */
/**
 * Add a node to the retired list of the thread
 * Input: 	td- the thread data
 * 			node- the retired node
 */
inline void addToRetList(ThreadData* td, Node* node);
/**
 * Try to delete nodes (this function is called when the number of
 * the retired nodes is greater that MAX_RETIRED_NODES).
 * Input: 	mm- the memory manager
 * 			tid- the tid of the thread
 */
inline void handleReclamationBuffer(MemoryManager* mm, long tid);
/**
 * Move all the retired nodes to a temp list
 * Input: 	td-the threads data (where the list of the retired nodes is located)
 * 			retval- the temp list
 */
inline void popAllRetList(ThreadData* td, RetiredNodes* retval);
/**
 * Free the memory of a list
 * Input: head- the head of the list that should be freed
 */
inline void freeEntryNodesList(RetiredNodes* head);
/**
 * Find if a retired node is still a HP
 * Input: 	head- the head of the HP list
 * 			entry- the entry to the retired node
 */
inline bool lookUpEntryNodesList(RetiredNodes* head, RetiredNodes* entry);

/**
 * if an HP is inside our temp retired nodes list, then remove it from temp
 * retNodes list and add it to the retired nodes list
 */
#define FIND_DEL_HP(HP) 													\
		if (NULL != (found = removeFromRetNodes(myRetiredNodes, HP))) { 	\
			addRetToRetList(&GET_THREAD_DATA(tid), found); 					\
			GET_THREAD_DATA(tid).rSize--;									\
		}

inline MemoryManager* getMemoryManager(unsigned int numOfThreads) {
	MemoryManager *mm = (MemoryManager*)malloc(sizeof(MemoryManager));
	mm->threadsData = (ThreadData*)malloc(sizeof(ThreadData)*numOfThreads*
			NUM_BYTES_IN_CACHE_LINE);
	memset(mm->threadsData, 0, sizeof(ThreadData)*numOfThreads*NUM_BYTES_IN_CACHE_LINE);
	for (int i=0; i<numOfThreads; ++i)
		INIT_LIST_HEAD(&(GET_THREAD_DATA(i).retiredNodesHead));
	mm->numThreads = numOfThreads;
	return mm;
}

inline void deleteMemoryManager(MemoryManager* mm) {
	list_t *curr_node, *q;
	for (int i=0; i<mm->numThreads; ++i) {
		list_for_each_safe(curr_node, q, &GET_THREAD_DATA(i).retiredNodesHead) {
			RetiredNodes* rn=list_entry(curr_node, RetiredNodes, npRetiredNode);
			free(rn->node);
			list_del(&rn->npRetiredNode);
			free(rn);
		}
	}
	free(mm->threadsData);
	free(mm);
}

inline void retireNode(MemoryManager* mm, Node* node, long tid) {
	addToRetList(&GET_THREAD_DATA(tid), node);
	if (GET_THREAD_DATA(tid).rSize > MAX_RETIRED_NODES)
		handleReclamationBuffer(mm, tid);
}

inline void addToRetList(ThreadData* td, Node* node) {
	RetiredNodes* rn = (RetiredNodes*)malloc(sizeof(RetiredNodes));
	rn->node = node;
	list_add_tail(&(rn->npRetiredNode), &(td->retiredNodesHead));
	td->rSize++;
}

/**
 * this macro adds a HP to plist
 */
#define ADD_HP_TO_TEMP_LIST(hp) 											\
		if (hp != NULL ) { 													\
			RetiredNodes* newEntry = 										\
			(RetiredNodes*) malloc(sizeof(RetiredNodes));					\
			newEntry->node = hp;											\
			list_add_tail(&newEntry->npRetiredNode, &plist.npRetiredNode);	\
		}

inline void handleReclamationBuffer(MemoryManager* mm, long tid) {
	// Local list for recording current hazard pointers
	RetiredNodes plist;
	INIT_LIST_HEAD(&plist.npRetiredNode);

	for (int i=0; i<mm->numThreads; ++i) {
		ADD_HP_TO_TEMP_LIST(GET_THREAD_DATA(i).hp0);
		ADD_HP_TO_TEMP_LIST(GET_THREAD_DATA(i).hp1);
		ADD_HP_TO_TEMP_LIST(GET_THREAD_DATA(i).hp2);
		ADD_HP_TO_TEMP_LIST(GET_THREAD_DATA(i).hp3);
	};
	//Stage2: Reclaim to-be-retired entries that are not protected by a HP
	RetiredNodes tmplist;
	RetiredNodes* entry = NULL;
	list_t* tmpEntry = NULL;

	//Copy all local to-be-retired entires and clear RetList
	popAllRetList(&GET_THREAD_DATA(tid), &tmplist);

	tmpEntry = list_pop(&tmplist.npRetiredNode);
	if (tmpEntry != NULL) {
		entry = list_entry(tmpEntry, RetiredNodes, npRetiredNode);
	}

	while ( (entry != NULL) && (tmpEntry != NULL) ) {
		if ( lookUpEntryNodesList(&plist, entry) ) {
			//protected entry, add back to reclemation list and try later
			list_add_tail(&entry->npRetiredNode,
								&(GET_THREAD_DATA(tid).retiredNodesHead));
		}
		else {
			if(entry != NULL){
				free(entry->node);
				free(entry);
				GET_THREAD_DATA(tid).rSize--;
			}
		}
		//unprotected entry, free
		tmpEntry = list_pop(&tmplist.npRetiredNode);
		entry = NULL;
		if (tmpEntry != NULL ) {
			entry = list_entry(tmpEntry, RetiredNodes, npRetiredNode);

		}
	}
	freeEntryNodesList(&plist);	// normal free. just deallocate all the entries.
}

inline RetiredNodes *removeFromRetNodes(RetiredNodes *rt, Node *node) {
	if (!rt || !node) return 0;

	list_t *currRet, *q;
	RetiredNodes *currRetNode;
	list_for_each_safe(currRet, q, &(rt->npRetiredNode)) {
		currRetNode = list_entry(currRet, RetiredNodes, npRetiredNode);
		if (node == currRetNode->node) {
			list_del(currRet);
			return currRetNode;
		}
	}
	return NULL;
}
inline void popAllRetList(ThreadData* td, RetiredNodes* retval) {
	INIT_LIST_HEAD(&retval->npRetiredNode);

	RetiredNodes* entry = NULL;
	list_t* tmpEntry = NULL;

	while ((tmpEntry = list_pop(&td->retiredNodesHead))) {
		entry = list_entry(tmpEntry, RetiredNodes, npRetiredNode);
		list_add_tail(&entry->npRetiredNode, &(retval->npRetiredNode));
	}

	return;
}

inline bool lookUpEntryNodesList(RetiredNodes* head, RetiredNodes* entry) {
	RetiredNodes* tmpEntry = NULL;
	list_t* pos = NULL;
	list_t* list1 = &head->npRetiredNode;
	list_for_each(pos,list1) {
		tmpEntry = list_entry(pos,RetiredNodes,npRetiredNode);
		if (tmpEntry->node == entry->node ) {
			return true;
		}
	}
	return false;
}

inline void freeEntryNodesList(RetiredNodes* head) {
	RetiredNodes* entry1 = NULL;
	list_t* pos = NULL;
	list_t* n = NULL;
	list_t* list1 = &head->npRetiredNode;
	list_for_each_safe(pos, n, list1) {
		entry1 = list_entry(pos,RetiredNodes,npRetiredNode);
		list_del(pos);
		free(entry1);
	}
}
